/**
 * Dispatch message. The most important service of this file is to split TCP
 * stream to command and command body.
 */

var proto = require("../Proto/proto.js");

var handlers = null;
function setCmdHandlers(hdlers) {
	"use strict";
	handlers = hdlers;
}

function defaultDataHandler(command, c) {
	"use strict";
	var cmd = command.cmd;
	console.error("defaultDataHandler, cmd:" + cmd);
	var handler = handlers[cmd];
	if (handler) {
		handler(command, c);
	} else {
		console.error("there is no handler for command:" + cmd);
	}
}

function resetParseParams(c) {
	"use strict";
	c.lengthSize = 0;
	c.cmdSize = 0;
	c.cmdBodyBufferIdx = 0;
	c.cmdSizeBufferIdx = 0;
	
	c.cmdBodyBuffer = null;
}

function getSizeByFirstByte(firstByte) {
	"use strict";
	/*jshint bitwise: false*/
	var sz = 1;
	while ((firstByte & 0x80) !== 0) {
		sz += 1;
		firstByte = firstByte << 1;
	}
	return sz;
}

function decodeInteger(buff) {
	"use strict";
	/*jshint bitwise: false*/
	var firstByte = buff[0];
	var sz = getSizeByFirstByte(firstByte);
	
	var value = firstByte & ((1 << (8-sz)) - 1);
	for(var idx = sz - 1; idx > 0; idx -= 1) {
		var x = buff[idx];
		value = value | (x << (idx*8 - sz));
	}

	if (value > 1024) {
		throw "command packet tOO large:" + value;
	}
	return value;
}

function parseBuffer(c, buffer) {
	"use strict";

	if (c.lengthSize === 0) {
		var firstByte = buffer[0];
		c.lengthSize = getSizeByFirstByte(firstByte);
	}
	
	if (c.cmdSize === 0) {
		if (c.cmdSizeBufferIdx + buffer.length >= c.lengthSize) {
			var needCmdSize = c.lengthSize - c.cmdSizeBufferIdx;
			buffer.copy(c.cmdSizeBuffer, c.cmdSizeBufferIdx, 0, needCmdSize);
			buffer = buffer.slice(needCmdSize);
			
			try {
				c.cmdSize = decodeInteger(c.cmdSizeBuffer);
			} catch (e) {
				console.error(e);
				c.destroy();
				return null;
			}
			
			c.cmdBodyBuffer = new Buffer(c.cmdSize);
		} else {
			buffer.copy(c.cmdSizeBuffer, c.cmdSizeBufferIdx, 0);
			c.cmdSizeBufferIdx += buffer.length;
			return null;
		}
	}

	if (buffer.length < 1) {
		return null;
	}
	
	if (buffer.length + c.cmdBodyBufferIdx >= c.cmdSize) {
		var needBodySize = c.cmdSize - c.cmdBodyBufferIdx;
		buffer.copy(c.cmdBodyBuffer, c.cmdBodyBufferIdx, 0, needBodySize);
		buffer = buffer.slice(needBodySize);
		
		var command = proto.parseBasicCmd(c.cmdBodyBuffer);
		c.dataHandler(command, c);
		
		resetParseParams(c);
		
		return buffer;
	} else {
		buffer.copy(c.cmdBodyBuffer, c.cmdBodyBufferIdx, 0);
		c.cmdBodyBufferIdx += buffer.length;
		
		return null;
	}
}

function dispatcher(c, dataHandler) {
	"use strict";
	if (!dataHandler) {
		dataHandler = defaultDataHandler;
	}

	c.dataHandler = dataHandler;

	c.cmdSizeBuffer = new Buffer(4);

	resetParseParams(c);

	c.on("data", function(buffer) {
		//console.error("received buffer, length:" + buffer.length);
		while (buffer !== null && buffer.length > 0) {
			buffer = parseBuffer(c, buffer);
		}
	});
}

dispatcher.defaultDataHandler = defaultDataHandler;
dispatcher.setCmdHandlers = setCmdHandlers;
module.exports = dispatcher;
